package demo.db.tsdb.prometheus;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import demo.vo.tsdb.Sample;
import io.prometheus.client.Collector;
import io.prometheus.client.CollectorRegistry;
import io.prometheus.client.Counter;
import io.prometheus.client.Gauge;
import io.prometheus.client.GaugeMetricFamily;
import io.prometheus.client.Histogram;
import io.prometheus.client.Summary;
import io.prometheus.client.exporter.HTTPServer;
import io.prometheus.client.exporter.PushGateway;
import io.prometheus.client.exporter.common.TextFormat;
import io.prometheus.client.hotspot.BufferPoolsExports;
import io.prometheus.client.hotspot.ClassLoadingExports;
import io.prometheus.client.hotspot.DefaultExports;
import io.prometheus.client.hotspot.GarbageCollectorExports;
import io.prometheus.client.hotspot.MemoryAllocationExports;
import io.prometheus.client.hotspot.MemoryPoolsExports;
import io.prometheus.client.hotspot.StandardExports;
import io.prometheus.client.hotspot.ThreadExports;
import io.prometheus.client.hotspot.VersionInfoExports;
import org.junit.Test;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;

/**
 * 指标接入方式:
 * <li>1. target自己采集指标,暴露出端口,  prometheusserver主动拉取数据
 * <li>2. target主动推送到pushGateway, prometheus主动去pushGateway拉取
 * <p>
 * target 暴露端口的方式
 * <li>1. 普通采集指标,暴露接口的方式
 * <li>2. 借助actuator和内置的micrometer, 然后使用prometheus-registry.
 * --对于springboot项目, 这种比较方便业务埋点. 从springboot2.X开始, actuator内部集成了micrometer
 * <p>
 * 基本度量类型:
 * <li>counter - 计数器，只增不减
 * <li>Gauge - 仪表盘，可增可减
 * <li>Histogram - 直方图，观察值分布情况
 * <li>Summary - 摘要，类似Histogram但提供滑动时间窗口
 */
public class PrometheusClientDemo {


    /**
     * 这里定义了一个名为my_guage的监控指标，该监控指标的所有样本数据均转换为一个MetricFamilySamples.Sample实例，
     * 该实例中包含了该样本的指标名称、标签名数组、标签值数组以及样本数据的值。
     * 需要注意的是MetricFamilySamples中所有样本的名称必须保持一致，否则生成的数据将无法符合Prometheus的规范。
     *
     * <p>
     * 直接使用MetricFamilySamples.Sample和MetricFamilySamples的方式适用于当某监控指标的样本之间的标签可能不一致的情况，
     * 例如，当监控容器时，不同容器实例可能包含一些自定义的标签，如果需要将这些标签反应到样本上，那么每个样本的标签则不可能保持一致。
     * 而如果所有样本的是一致的情况下，我们还可以使用client_java针对不同指标类型的实现GaugeMetricFamily，CounterMetricFamily，SummaryMetricFamily等。
     */
    public void testCollector() {
        Collector collector1 = new Collector() {
            @Override
            public List<MetricFamilySamples> collect() {
                List<MetricFamilySamples> mfs = new ArrayList<>();

                String metricName = "my_guage_1";

                // Your code to get metrics

                MetricFamilySamples.Sample sample = new MetricFamilySamples.Sample(metricName, Arrays.asList("l1"), Arrays.asList("v1"), 4);
                MetricFamilySamples.Sample sample2 = new MetricFamilySamples.Sample(metricName, Arrays.asList("l1", "l2"), Arrays.asList("v1", "v2"), 3);

                MetricFamilySamples samples = new MetricFamilySamples(metricName, Type.GAUGE, "help", Arrays.asList(sample, sample2));

                mfs.add(samples);
                return mfs;
            }
        };

        collector1.collect();

        Collector collector2 = new Collector() {
            @Override
            public List<MetricFamilySamples> collect() {
                List<MetricFamilySamples> mfs = new ArrayList<>();

                // With no labels.
                mfs.add(new GaugeMetricFamily("my_gauge_2", "help", 42));

                // With labels
                GaugeMetricFamily labeledGauge = new GaugeMetricFamily("my_other_gauge", "help", Arrays.asList("labelname"));
                labeledGauge.addMetric(Arrays.asList("foo"), 4);
                labeledGauge.addMetric(Arrays.asList("bar"), 5);
                mfs.add(labeledGauge);

                return mfs;
            }
        };
        collector2.register();
        collector2.collect();
    }


    /**
     * 在启动之前，别忘记调用Collector的register()方法。否则HTTPServer是找不到任何的Collector实例。
     * 当调用Collector实例register()方法时，会将该实例保存到CollectorRegistry当中，CollectorRegistry负责维护当前系统中所有的Collector实例。
     * HTTPServer在接收到HTTP请求之后，会从CollectorRegistry中拿到所有的Collector实例，并调用其collect()方法获取所有样本，最后格式化为Prometheus的标准输出。
     * <p>
     * 除了直接使用HTTPServer以外暴露样本数据以外，client_java中还提供了对Spring Boot、Spring Web以及Servlet的支持。
     *
     * @throws IOException
     */
    public void testHTTPEServer() throws IOException {
        HTTPServer server = new HTTPServer(1234);
    }

    /**
     * 使用内置的Collector.
     * <p>
     * 通过client_java中定义的标准接口，用户可以快速实现自己的监控数据收集器，并通过HTTPServer将样本数据输出给Prometheus。
     * 除了提供接口规范以外，client_java还提供了多个内置的Collector模块，以simpleclient_hotspot为例，
     * 该模块中内置了对JVM虚拟机运行状态（GC，内存池，JMX，类加载，线程池等）数据的Collector实现，
     */
    public void testInnerCollector() {
        // 通过调用io.prometheus.client.hotspot.DefaultExport的initialize方法注册该模块中所有的Collector实例
        DefaultExports.initialize();

    }

    /**
     * 对于一些短周期或者临时采集的样本数据，client_java还提供了对PushGateway的支持.
     * PushGateway的实现类可以从所有注册到defaultRegistry的Collector实例中获取样本数据并直接推送 到外部部署的PushGateway服务中
     *
     * @throws IOException
     */
    public void testPushGateway() throws IOException {
        CollectorRegistry registry = CollectorRegistry.defaultRegistry;
        PushGateway pg = new PushGateway("127.0.0.1:9091");
        pg.pushAdd(registry, "my_batch_job");
    }

    /**
     * 在业务代码中进行监控埋点.
     * 在client_java中除了使用Collector直接采集样本数据以外，还直接提供了对Prometheus中4种监控类型的实现分别是：Counter、Gauge、Summary和Histogram。
     * 基于这些实现，开发人员可以非常方便的在应用程序的业务流程中进行监控埋点。
     * Counter 是一个单调递增的计数器，只能增加不能减少
     */
    @Test
    public void testCounter() {
        // 定义一个Counter类型的监控指标,
        Counter requests = Counter.build()
                .name("requests")
                .help("Total requests.")
                .register();
        requests.inc();
        requests.inc(3.0);
        double value = requests.get();
        System.out.println(value);
        List<Collector.MetricFamilySamples> metricFamilySamples = requests.collect();
        System.out.println(JSON.toJSONString(metricFamilySamples, SerializerFeature.DisableCircularReferenceDetect));
        metricFamilySamples.forEach(s -> System.err.println(JSON.toJSONString(s, true)));

        // 带有标签的计数器
        Counter counterWithLabel = Counter.build()
                .name("request_xx")// 指标名称
                .help("各个接口的请求量")// 帮助信息
                .labelNames("method", "endpoint")// 定义标签
                .register();// 注册到默认注册表
        counterWithLabel.labels("GET", "/api/v1/user").inc();
        counterWithLabel.labels("POST", "api/v1/form").inc();

        metricFamilySamples = counterWithLabel.collect();
        System.out.println(JSON.toJSONString(metricFamilySamples, SerializerFeature.DisableCircularReferenceDetect));
        metricFamilySamples.forEach(s -> System.err.println(JSON.toJSONString(s, true)));
        for (Collector.MetricFamilySamples familySamples : metricFamilySamples) {
            String name = familySamples.name;
            List<Collector.MetricFamilySamples.Sample> list = familySamples.samples;
            for (Collector.MetricFamilySamples.Sample sample : list) {
                String sampleName = sample.name;
                List<String> labelNames = sample.labelNames;
                List<String> labelValues = sample.labelValues;
                double sampleValue = sample.value;
                Long timestampMs = sample.timestampMs;
                System.out.println(JSON.toJSONString(sample));
            }
        }
    }

    /**
     * Prometheus 的默认注册表 (Default Registry) 是用于管理和收集所有度量指标的中央存储机制。
     */
    @Test
    public void testCollectorRegistry() throws IOException {
        CollectorRegistry defaultRegistry = CollectorRegistry.defaultRegistry;
        CollectorRegistry registry = new CollectorRegistry();
        // 清除所有收集器
        registry.clear();

        // 清除特定收集器
        Counter counter = Counter.build()
                .name("my_counter")
                .help("My counter")
                .register(registry);
        registry.unregister(counter);
        // 注册默认的 JVM 指标
        DefaultExports.initialize();
        // 或者选择性注册
        (new StandardExports()).register(registry);
        (new MemoryPoolsExports()).register(registry);
        (new MemoryAllocationExports()).register(registry);
        (new BufferPoolsExports()).register(registry);
        (new GarbageCollectorExports()).register(registry);
        (new ThreadExports()).register(registry);
        (new ClassLoadingExports()).register(registry);
        (new VersionInfoExports()).register(registry);
        // 获取所有指标样本
        Enumeration<Collector.MetricFamilySamples> samples = registry.metricFamilySamples();
        // 处理样本
        while (samples.hasMoreElements()) {
            Collector.MetricFamilySamples family = samples.nextElement();
            // 处理每个样本
        }
        // 导出指标
        Enumeration<Collector.MetricFamilySamples> metricFamilySamples = registry.metricFamilySamples();
        Writer writer = new StringWriter();
        TextFormat.write004(writer, metricFamilySamples);
        System.out.println(writer);
    }


    /**
     * Collector.MetricFamilySamples 是 Prometheus 客户端库中用于表示指标族样本的核心类。
     */
    @Test
    public void testMetricFamilySamples() {
        List<Collector.MetricFamilySamples.Sample> samples = new ArrayList<>();
        samples.add(new Collector.MetricFamilySamples.Sample("test_metric",
                Arrays.asList("label1"), Arrays.asList("value1"), 123.45
        ));
        Collector.MetricFamilySamples metricFamilySamples = new Collector.MetricFamilySamples(
                "test_metric",
                Collector.Type.GAUGE,
                "Test metric help",
                samples
        );

        String name = metricFamilySamples.name;// 指标名称
        Collector.Type type = metricFamilySamples.type;// 指标类型
        String help = metricFamilySamples.help;// 帮助信息
        List<Sample<Double>> list = Sample.from(metricFamilySamples);
        System.out.println(JSON.toJSONString(list, true));
    }

    public String formatMetrics(Collector.MetricFamilySamples family) {
        StringBuilder sb = new StringBuilder();

        // 添加注释
        sb.append("# HELP ").append(family.name).append(" ")
                .append(family.help).append("\n");

        // 添加类型
        sb.append("# TYPE ").append(family.name).append(" ")
                .append(family.type.name().toLowerCase()).append("\n");

        // 添加样本
        for (Collector.MetricFamilySamples.Sample sample : family.samples) {
            sb.append(sample.name);

            // 添加标签
            if (!sample.labelNames.isEmpty()) {
                sb.append("{");
                for (int i = 0; i < sample.labelNames.size(); i++) {
                    if (i > 0) sb.append(",");
                    sb.append(sample.labelNames.get(i)).append("=\"")
                            .append(sample.labelValues.get(i)).append("\"");
                }
                sb.append("}");
            }

            sb.append(" ").append(Collector.doubleToGoString(sample.value))
                    .append("\n");
        }

        return sb.toString();
    }

    /**
     * 简单类型Gauge和Counter.
     * <li>Gauge对象主要包含两个方法inc()和dec()，分别用于计数器+1和-1。
     * <li>Counter与Gauge的使用方法一致，唯一的区别在于Counter实例只包含一个inc()方法，用于计数器+1。
     */
    public static class TestGauge {
        // 定义一个Gauge类型的监控指标，该指标的名称是my_gauge
        static final Gauge inprogressRequests = Gauge.build()
                .name("inprogress_requests")
                .labelNames("method")
                .help("Inprogress requests.")
                .register();

        void processRequest2() {
            inprogressRequests.labels("get").inc();
            // Your code here.
            inprogressRequests.labels("get").dec();
        }
    }

    /**
     * 复杂类型Summary和Histogram.
     * Summary和Histogram用于统计和分析样本的分布情况。
     * 如下所示，通过Summary可以将HTTP请求的字节数以及请求处理时间作为统计样本，直接统计其样本的分布情况。
     */
    public static class TestSummary {

        // 定义一个Summary类型的监控指标
        private static final Summary requestLatency = Summary.build()
                .name("requests_latency_seconds")
                .help("request latency in seconds")
                .register();

        private static final Summary receivedBytes = Summary.build()
                .name("requests_size_bytes")
                .help("request size in bytes")
                .register();
        //        Summary和Histogram的用法基本保持一致，区别在于Summary可以指定在客户端统计的分位数
        Summary requestLatency2 = Summary.build()
                .quantile(0.5, 0.05)   // 其中0.05为误差
                .quantile(0.9, 0.01)   // 其中0.01为误差
                .name("requests_latency_seconds")
                .help("Request latency in seconds.")
                .register();

        public void processRequest(HttpServletRequest req) {
            Summary.Timer requestTimer = requestLatency.startTimer();
            try {
                // Your code here.
            } finally {
                requestTimer.observeDuration();
                receivedBytes.observe(req.getContentLength());
            }
            // Summary实例也提供了timer()方法，可以对线程或者Lamda表达式运行时间进行统计：
            requestLatency.time(new Runnable() {
                @Override
                public void run() {
                    // your code here
                }
            });
        }

    }

    /**
     * 对于Histogram而言，默认的分布桶为[.005, .01, .025, .05, .075, .1, .25, .5, .75, 1, 2.5, 5, 7.5, 10]，
     * 如果需要指定自定义的桶分布，可以使用buckets()方法指定，如下所示：
     */
    public static class TestHistogram {

        // 定义一个Histogram类型的监控指标，该指标的名称是my_histogram

        static final Histogram requestLatency2 = Histogram.build()
                .name("requests_latency_seconds")
                .help("Request latency in seconds.")
                .register();
        //        You can choose your own buckets:linearBuckets and exponentialBuckets offer easy ways to set common bucket patterns.
        static final Histogram requestLatency = Histogram.build()
                .buckets(0.01, 0.02, 0.03, 0.04)
                .name("requests_latency_seconds")
                .help("Request latency in seconds.")
                .register();

        void processRequest2(HttpServletRequest req) {
            Histogram.Timer requestTimer = requestLatency2.startTimer();
            try {
                // Your code here.
            } finally {
                requestTimer.observeDuration();
            }
        }


    }

}
